#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "dos.h"

#define Unknown 0x00			/* unknown header value */
#define Header 0x01			/* Header header value */
#define Track 0x02				/* Track header value */

#define Single 0x00			/* format single multi-channel */
#define Simult 0x01			/* format simultaneous tracks */
#define Sequen 0x02 			/* format multiple sequential */

#define SysExFirst 0xF0
#define SysExNext 0xF7
#define MetaEvent 0xFF

#define SeqNumber 0x00
#define TextEvent 0x01
#define Copyright 0x02
#define SqTrkName 0x03
#define InstName 0x04
#define Lyric 0x05
#define Marker 0x06
#define CuePoint 0x07
#define MCPrefix 0x20
#define EndOfTrack 0x2F
#define SetTempo 0x51
#define SMPTEOffset 0x54
#define TimeSig 0x58
#define SqSpcMeta 0x7F
#define NoteOff1 0x80
#define NoteOff16 0x8f
#define NoteOn1 0x90
#define NoteOn16 0x9f
#define PolyPress1 0xa0
#define PolyPress16 0xaf
#define ControlChange1 0xb0
#define ControlChange16 0xbf
#define ProgramChange1 0xc0
#define ProgramChange16 0xcf
#define ChanPress1 0xd0
#define ChanPress16 0xdf
#define PitchWheel1 0xe0
#define PitchWheel16 0xef

FILE	*infile, *outfile;  /* input & output files */

unsigned char Head[4];			/* chunk title string */
unsigned char HType;			/* chunk type */
unsigned long HLength;			/* chunk length */
unsigned int FrmType;			/* Format type */
unsigned char NTrks=1;			/* Number of Tracks */
unsigned int Division;			/* Division */
unsigned long Tempo;			/* Tempo */
unsigned int SNumber;
unsigned char ChannelRef=0;
unsigned char EndTrack=0;
unsigned long DeltaTime;
unsigned char Note=0x80;
unsigned long Duration=0;
unsigned long TTimes[64];		/* Deltas between Tempo changes */
unsigned long Tempos[64];		/* Tempo changes */
unsigned long GlobalTime;		/* overall time elpased in ticks */
unsigned char TempoCt;			/* number of tempos encountered */


unsigned char GetMIDI(void)

{
	unsigned char byte;
	byte = getc(infile);
	return(byte);
}

unsigned long ReadVarLen(void)

{
	unsigned long value=0;
	unsigned char c;
	do
	{
		c = GetMIDI();
		value = (value << 7) + (c & 0x7F);
	} while(c > 127);

	return(value);
}


void GetMIDIhunkHeader(void)		/* read & check chunk header */

{
	unsigned char l,byte;
	for (l=0; l<4; l++)
	{
		byte = GetMIDI();
		Head[l] = byte;
	}
	if (Head[0]=='M')
		if (Head[1]=='T')
			if (Head[2]=='h')
				if (Head[3]=='d')
					HType=Header;
				else
					HType=Unknown;
		   	else
				if (Head[2]=='r')
					if (Head[3]=='k')
						HType=Track;
					else
						HType=Unknown;
				else
					HType=Unknown;
		else
			HType=Unknown;
	else
		HType=Unknown;

	HLength = 0;
	for (l=0; l<4; l++)		/* get 4 byte length */
	{
		byte = GetMIDI();
		HLength = (HLength << 8) + byte;
	}
}

unsigned int GetWord(void)

{
	unsigned int W;
	W = (GetMIDI() << 8) + GetMIDI();
	return (W);
}

unsigned long Get24bit(void)

{
	unsigned long W;
	W = (GetMIDI() << 16) + (GetMIDI() << 8) + GetMIDI();
	return (W);
}

void DoTextEvent(unsigned long TextLength)

{
	unsigned long l;
	unsigned char byte, line=0;

	fprintf(outfile,"Text Length: %u\n",TextLength);

	for (l=0; l<TextLength; l++)
	{
		byte = GetMIDI();
		if ((byte < 32) || (byte > 127))
			byte = '.';
		fprintf(outfile,"%c",byte);
		if (++line == 64)
			{
				fprintf(outfile,"\n;");
				line = 0;
			}
	}
	fprintf(outfile,"\n");
}

void SkipData(unsigned long DataLength)

{
	unsigned long l;
	unsigned char byte, line=0;

	fprintf(outfile,"Skipping Data of Length: %u\n",DataLength);

	for (l=0; l<DataLength; l++)
	{
		byte = GetMIDI();
		if ((byte < 32) || (byte > 127))
			byte = '.';
		fprintf(outfile,"%c",byte);
		if (++line == 64)
			{
				fprintf(outfile,"\n");
				line = 0;
			}
	}
	fprintf(outfile,"\n");
}

void TurnNoteOff(unsigned char channel)

{
	unsigned char NoteNumber, Release;
	ChannelRef=channel;
	NoteNumber = GetMIDI();
	Release = GetMIDI();
	fprintf(outfile,"Chan: %u Note: %u Release: %u\n",channel,NoteNumber,Release);
}

void TurnNoteOn(unsigned char channel)

{
	unsigned char NoteNumber, Attack;
	ChannelRef=channel;
	NoteNumber = GetMIDI();
	Attack = GetMIDI();
	fprintf(outfile,"Chan: %u Note: %u Attack: %u\n",channel,NoteNumber,Attack);
}

void DoRunStatus(unsigned char NoteNumber)

{
	unsigned char Attack;
	Attack = GetMIDI();
	fprintf(outfile,"Chan: %u Note: %u Attack: %u\n",ChannelRef,NoteNumber,Attack);
}

void DoPolyPress(unsigned char channel)

{
	unsigned char NoteNumber, Pressure;
	ChannelRef=channel;
	NoteNumber = GetMIDI();
	Pressure = GetMIDI();
	fprintf(outfile,"Chan: %u Note: %u Pressure: %u\n",channel,NoteNumber,Pressure);
}

void DoControlChange(unsigned char channel)

{
	unsigned char ControllerID, ControllerValue;
	ChannelRef=channel;
	ControllerID = GetMIDI();
	ControllerValue = GetMIDI();
	fprintf(outfile,"Chan: %u CtlrID: %u CtlrV: %u\n",channel,ControllerID,ControllerValue);
}

void DoProgramChange(unsigned char channel)

{
	unsigned char ProgramNumber;
	ChannelRef=channel;
	ProgramNumber = GetMIDI();
	fprintf(outfile,"Chan: %u ProgramChange: %u\n",channel,ProgramNumber);
}

void DoPitchWheel(unsigned char channel)

{
	unsigned int PitchBend;
	ChannelRef=channel;
	PitchBend = (GetMIDI() << 8) + GetMIDI();
	fprintf(outfile,"Chan: %u PitchWheel: %u\n",channel,PitchBend);
}

void DoChanPress(unsigned char channel)

{
	unsigned char PressureValue;
	ChannelRef=channel;
	PressureValue = GetMIDI();
	fprintf(outfile,"Chan: %u Pressure: %u\n",channel,PressureValue);
}

void GetSNumber(void)

{
	SNumber = GetWord();
	fprintf(outfile,"Sequence Number: %u\n",SNumber);
}

void GetMIDIChannelRef(void)

{
	ChannelRef = GetMIDI();
	fprintf(outfile,"Channel Reference: %u\n",ChannelRef);
}

void GetSMPTE(void)

{
	unsigned char hours,mins,secs,frames,fracframes;

	hours = GetMIDI();
	mins = GetMIDI();
	secs = GetMIDI();
	frames = GetMIDI();
	fracframes = GetMIDI();
	fprintf(outfile,"SMPTE: %u,%u,%u,%u,%u\n",hours,mins,secs,frames,fracframes);
}

void GetTimeSig(void)

{
	unsigned char nume,denom,clks,note32nd;

	nume = GetMIDI();
	denom = GetMIDI();
	clks = GetMIDI();
	note32nd = GetMIDI();
	fprintf(outfile,"Time Sig: %u,%u,%u,%u\n",nume,denom,clks,note32nd);
}

void GetTempo(void)

{
	Tempo = Get24bit();
	fprintf(outfile,"Tempo: %u\n",Tempo);
	TTimes[TempoCt]=GlobalTime;	/* store tempo time */
	Tempos[TempoCt++]=Tempo;	/* store tempo in tempo map */
}

void DoEndTrack(void)

{
	EndTrack = 1;
	fprintf(outfile,"End Of Track\n");
}

void ReadMThd(void)

{
	unsigned int l,byte;
	GetMIDIhunkHeader();			/* read chunk header info */
	FrmType = GetWord();			/* fetch file format 0,1,2 */
	NTrks = GetWord();			/* fetch number of tracks */
	Division = GetWord();			/* fetch division time info */
	if (HLength > 6)			/* skip any extra bytes */
	{
		l = HLength - 6;
		do
		{
			byte=GetMIDI();
			fprintf(outfile,",%u",byte);
			l--;
		} while(l > 0);
	}

	if (HType==Header)
		fprintf(outfile,"MThd\n");
	else
	if (HType==Track)
		fprintf(outfile,"MTrk\n");
	else
		fprintf(outfile,"Unknown\n");
	fprintf(outfile,"Length  :%u\n", HLength);
	fprintf(outfile,"Format  :%u\n", FrmType);
	fprintf(outfile,"#Tracks :%u\n", NTrks);
	fprintf(outfile,"Division:%u\n", Division);

}

void ReadTempoMTrk(void)

{
	unsigned char Event;
	unsigned char EventType;
	unsigned long EventLen;
	unsigned char Channel;
	unsigned int n;
	GlobalTime=0;
	TempoCt=0;
		EndTrack=0;
		GetMIDIhunkHeader();			/* read chunk header info */

		if (HType==Header)
			fprintf(outfile,"\nMThd\n");
		else
		if (HType==Track)
			fprintf(outfile,"\nMTrk\n");
		else
		if (HType==Unknown)
			fprintf(outfile,"\nUnknown\n");
		fprintf(outfile,"Length  :%u\n", HLength);
	
	
		do
		{
			DeltaTime=ReadVarLen();			/* fetch delta time */
			GlobalTime+=DeltaTime;		/* add delta to time */
			fprintf(outfile,"Delta = :%u  ", DeltaTime);
			Event=GetMIDI();
			EventType=Event & 0xF0;
			Channel=Event & 0x0F;
	
			if (Event < 0x80)
				DoRunStatus(Event);
			else
			if (Event == MetaEvent)
			{
				EventType=GetMIDI();
				EventLen=ReadVarLen();
	
				if (EventType == SeqNumber)
					GetSNumber();
				else
				if (EventType == TextEvent)
					DoTextEvent(EventLen);
				else
				if (EventType == Copyright)
					DoTextEvent(EventLen);
				else
				if (EventType == SqTrkName)
					DoTextEvent(EventLen);
				else
				if (EventType == InstName)
					DoTextEvent(EventLen);
				else
				if (EventType == Lyric)
					DoTextEvent(EventLen);
				else
				if (EventType == Marker)
					DoTextEvent(EventLen);
				else
				if (EventType == CuePoint)
					DoTextEvent(EventLen);
				else
				if (EventType == MCPrefix)
					GetMIDIChannelRef();
				else
				if (EventType == SetTempo)
					GetTempo();
				else
				if (EventType == SMPTEOffset)
					GetSMPTE();
				else
				if (EventType == TimeSig)
					GetTimeSig();
				else
				if (EventType == SqSpcMeta)
					SkipData(EventLen);
				else
				if (EventType == EndOfTrack)
					DoEndTrack();
			}
			else
			{
	
				if (EventType == NoteOff1)
					TurnNoteOff(Channel);
				else
				if (EventType == NoteOn1)
					TurnNoteOn(Channel);
				else
				if (EventType == PolyPress1)
					DoPolyPress(Channel);
				else
				if (EventType == ControlChange1)
					DoControlChange(Channel);
				else
				if (EventType == ProgramChange1)
					DoProgramChange(Channel);
				else
				if (EventType == ChanPress1)
					DoChanPress(Channel);
				else
				if (EventType == PitchWheel1)
					DoPitchWheel(Channel);
			}
	
		} while(!EndTrack);
}

void ReadMTrk(void)

{
	unsigned char Event;
	unsigned char EventType;
	unsigned long EventLen;
	unsigned char Channel;
	unsigned int n;
	GlobalTime=0;
	TempoCt=0;
	for (n=0; n<NTrks-1; n++)
	{
		EndTrack=0;
		GetMIDIhunkHeader();			/* read chunk header info */

		if (HType==Header)
			fprintf(outfile,"\nMThd\n");
		else
		if (HType==Track)
			fprintf(outfile,"\nMTrk\n");
		else
		if (HType==Unknown)
			fprintf(outfile,"\nUnknown\n");
		fprintf(outfile,"Length  :%u\n", HLength);
	
	
		do
		{
			DeltaTime=ReadVarLen();			/* fetch delta time */
			GlobalTime+=DeltaTime;		/* add delta to time */
			fprintf(outfile,"Delta = :%u  ", DeltaTime);
			Event=GetMIDI();
			EventType=Event & 0xF0;
			Channel=Event & 0x0F;
	
			if (Event < 0x80)
				DoRunStatus(Event);
			else
			if (Event == MetaEvent)
			{
				EventType=GetMIDI();
				EventLen=ReadVarLen();
	
				if (EventType == SeqNumber)
					GetSNumber();
				else
				if (EventType == TextEvent)
					DoTextEvent(EventLen);
				else
				if (EventType == Copyright)
					DoTextEvent(EventLen);
				else
				if (EventType == SqTrkName)
					DoTextEvent(EventLen);
				else
				if (EventType == InstName)
					DoTextEvent(EventLen);
				else
				if (EventType == Lyric)
					DoTextEvent(EventLen);
				else
				if (EventType == Marker)
					DoTextEvent(EventLen);
				else
				if (EventType == CuePoint)
					DoTextEvent(EventLen);
				else
				if (EventType == MCPrefix)
					GetMIDIChannelRef();
				else
				if (EventType == SqSpcMeta)
					SkipData(EventLen);
				else
				if (EventType == EndOfTrack)
					DoEndTrack();
			}
			else
			{
	
				if (EventType == NoteOff1)
					TurnNoteOff(Channel);
				else
				if (EventType == NoteOn1)
					TurnNoteOn(Channel);
				else
				if (EventType == PolyPress1)
					DoPolyPress(Channel);
				else
				if (EventType == ControlChange1)
					DoControlChange(Channel);
				else
				if (EventType == ProgramChange1)
					DoProgramChange(Channel);
				else
				if (EventType == ChanPress1)
					DoChanPress(Channel);
				else
				if (EventType == PitchWheel1)
					DoPitchWheel(Channel);
			}
	
		} while(!EndTrack);
	}
}

int main(int argc, char *argv[])

{
	if (argc != 3)
	{
		printf("MIDI2TXT: convert file\n");
		printf("Usage: MIDI2TXT infile outfile\n");
		return EXIT_FAILURE;
	}
	if ((infile = fopen(argv[1], "rb")) == NULL)
	{
		fprintf(stderr, "MIDI2TXT: Unable to open file: %s\n", argv[1]);
		return EXIT_FAILURE;
	}

	if ((outfile = fopen(argv[2], "w")) == NULL)
	{
		fprintf(stderr, "MIDI2TXT: Unable to create file: %s\n", argv[2]);
		return EXIT_FAILURE;
	}

	ReadMThd();
 	ReadTempoMTrk();
 	ReadMTrk();

	fclose(infile);
	fclose(outfile);

	return EXIT_SUCCESS;
}

